import java.util.ArrayList;


//------------------------------------------------------------//
// Time		|    Action							 | Personne	  //
//------------------------------------------------------------//
//			|Creation of the class				 |			  //
//12/07/2013|Implement kNearestNeighbour()		 | JP. Do	  //
//			|Implement calculateBurningSureface()|			  //
//------------------------------------------------------------//
//25/07/2013|Debuging		 					 | E. Faucher //
//------------------------------------------------------------//

/**
 * PlusProchesVoisins
 * ----------------------------------
 * This class is an implementation of 
 * K-NN algo(K-NearestNeighbour). It will predict 
 * for the new data if it is in fire and the 
 * amount of hectar burn.
 * 
 * For more specific algorithme information,
 * please read the rapport. 
 * 
 * @author Jean-Paul Do
 *
 */
public class PlusProchesVoisins {
	private double distanceEuclidienne;	//this is the euclidian distance
	private double distanceBrulee;		//the hectar burn
	private double sumDenominator;		//this variable is used to Σ of denominator
	private double sumNumerator;		//this variable is used to Σ of numerator
	private ArrayList<Data> nearestData;//this variable will stock the nearest neighbour
	private int countHasFire;			//this variable is used to find if the predict data has fire or not
	private int majority;				//this variable is used to check the majority of fire
	private int index;
	private double distance;
	
	/**
	 * kNearestNeighbour()
	 * ------------------------------------------
	 * This method is used to spot in with category (hasFire or not).
	 * It is the implementation of K-le plus proches voisins view in 
	 * the class. This method apply the euclidian distance to find the
	 * nearest neighbour.
	 * 
	 * @param trainingData
	 * 		the base knowlegde
	 * @param data
	 * 		the data that we need to predict
	 * @return int
	 * 		Error_code | Ok_code
	 */
	public int kNearestNeighbour(ArrayList<Data> trainingData, Data data){
		//check if we have empty data
		if (trainingData.isEmpty() || data == null){
			return Constant.ERROR;
		}
		
		//reset the nearest data
		if(nearestData == null)
			nearestData = new ArrayList<Data>();
		else
			nearestData.clear();
		
		//we need to calculate the euclidian distance for each known data
		for (int idx = 0; idx < trainingData.size(); idx ++){
			//reset the value of D(X,Xi)
			distanceEuclidienne = 0;
			
			//set the new D(X,Xi)
			distanceEuclidienne += Math.pow(trainingData.get(idx).getMonth() - data.getMonth(), 2);
			distanceEuclidienne += Math.pow(trainingData.get(idx).getCelcius() - data.getCelcius(), 2);
			distanceEuclidienne += Math.pow(trainingData.get(idx).getHumidity() - data.getHumidity(), 2);
			distanceEuclidienne += Math.pow(trainingData.get(idx).getRain() - data.getRain(), 2);
			distanceEuclidienne += Math.pow(trainingData.get(idx).getWindSpeed() - data.getWindSpeed(), 2);
			
			//set the euclidienne distance for each data
			trainingData.get(idx).setDistanceEuc(Math.pow(distanceEuclidienne,0.5));
			
			//adding the information
			nearestData.add(trainingData.get(idx));
			
			//check if the we have more neighbour than K neighbour
			if(nearestData.size() > Constant.K ){
				index = -1;
				distance = -1;
				for (int k = 0; k < nearestData.size(); k++ )
					if(nearestData.get(k).getDistanceEuc() > distance)
					{
						distance = nearestData.get(k).getDistanceEuc();
						index = k;
					}// remove the farther neighbour
				nearestData.remove(index);
			} //end if
		}//end for
		
		//set the variable
		majority = (int) (Constant.K/2)+1;
		countHasFire = 0;
		
		// count how many fire are nearby this predict data
		for(int idx = 0 ; idx < Constant.K; idx++){
			if(nearestData.get(idx).isFire())
				countHasFire ++;
		}
		
		// check if we have fire in this predict information 
		if(countHasFire >= majority)
			data.setHasFire(true);
		else
			data.setHasFire(false);
		
		//check the burning surface
		data.setBurnSurface(calculateBurningSureface(trainingData, data));
		
		return Constant.OK;
	}
	
	
	/**
	 * calculateBurningSureface()
	 * -----------------------------------------
	 * This method is used to found the burnning surface for the predict data.
	 * 
	 * Using this formula: F(X)= Σ[(1/D(X,Xi)²) * F(Xi)]/Σ(1/D(X,Xi)²)
	 * 
	 * @param trainingData
	 * 		the data use for traning 
	 * @param data
	 * 		the new data that we try to insert
	 * @return double
	 * 		the surface that has been burn
	 * 
	 */
	private double calculateBurningSureface(ArrayList<Data> trainingData, Data data){
		//We have 0 burning surface if we dont have fire
		if(!data.isFire())
			return 0;
		
		// set all variable to 0
		distanceBrulee = 0;
		sumDenominator = 0;
		sumNumerator = 0;
		
		// do the Σ for all the data
		for (int idx = 0; idx < trainingData.size(); idx ++){
			//reset the value of D(X,Xi)²
			distanceEuclidienne = Math.pow(trainingData.get(idx).getDistanceEuc(),2);
			
			//check if we have 0 as euclidien distance
			if(distanceEuclidienne == 0)
			{
				distanceEuclidienne = Constant.ZERO;
			}
			
			//the numerator: Σ[(1/D(X,Xi)²) * F(Xi)]
			sumNumerator   += (1/distanceEuclidienne) * trainingData.get(idx).getBurnSurface();
			
			//the denominator: Σ(1/D(X,Xi)²)
			sumDenominator +=  1/distanceEuclidienne;			
		}
		
		//the result of the burning surface
		distanceBrulee = sumNumerator/sumDenominator;
		
		
		
		return Utility.roundingDouble(distanceBrulee,2);
	}//calculateBurningSureface()
}
